{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# Grid / pack"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1",
   "metadata": {},
   "source": [
    "## Grid\n",
    "\n",
    "\n",
    "The ``gf.grid()`` function can take a list (or 2D array) of objects and arrange them along a grid. This is often useful for making parameter sweeps.\n",
    "The grid is arranged in a way that ensures that the elements can not  touch each other, with a `spacing` distance between them.\n",
    "Spacing can be a scalar or a tuple of two values, for different spacing in the x and y directions.\n",
    "\n",
    "The `align_x`/`align_y` arguments specify intra-row/intra-column alignment. Available options are `origin`, `xmin`, `xmax`, and `center`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "\n",
    "components_list = []\n",
    "for width1 in [1, 6, 9]:\n",
    "    for width2 in [1, 2, 4, 8]:\n",
    "        D = gf.components.taper(length=10, width1=width1, width2=width2, layer=(1, 0))\n",
    "        components_list.append(D)\n",
    "\n",
    "c = gf.grid(\n",
    "    tuple(components_list),\n",
    "    spacing=(1, 1),\n",
    "    shape=(3, 4),\n",
    "    align_x=\"x\",\n",
    "    align_y=\"y\",\n",
    ")\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3",
   "metadata": {},
   "source": [
    "## Pack\n",
    "\n",
    "The `gf.pack()` function packs geometries together into rectangular bins. If a `max_size` is specified, the function will create as many bins as necessary to pack all the geometries and then return a list of the filled-bin Components.\n",
    "\n",
    "> ⚠️ **Warning:** Unlike `gf.grid()`, which returns a single Component, `gf.pack()` returns a **list of Components** (one for each filled bin). Ensure your workflow accounts for this difference.\n",
    "\n",
    "Here we generate several random shapes and pack them together automatically.\n",
    "We allow the bin to be as large as needed in order to fit all the components by specifying `max_size = (None, None)`.\n",
    "By setting `aspect_ratio = (2,1)`, we specify that the rectangular bin that it tries to pack them into is set be two times as wide as it is tall:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import gdsfactory as gf\n",
    "\n",
    "np.random.seed(5)\n",
    "D_list = [gf.components.rectangle(size=(i, i)) for i in range(1, 10)]\n",
    "\n",
    "D_packed_list = gf.pack(\n",
    "    D_list,  # Must be a list or tuple of components.\n",
    "    spacing=1.25,  # Minimum distance between adjacent shapes.\n",
    "    aspect_ratio=(2, 1),  # (width, height) ratio of the rectangular bin.\n",
    "    max_size=(None, None),  # Limits the size into which the shapes will be packed.\n",
    "    density=1.05,  # Values closer to 1 pack tighter but require more computation.\n",
    "    sort_by_area=True,  # Pre-sorts the shapes by area.\n",
    ")\n",
    "D = D_packed_list[0]  # Only one bin was created, so we plot that one.\n",
    "D.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5",
   "metadata": {},
   "source": [
    "Say we need to pack many shapes into multiple 500x500 unit die. If we set ``max_size = (500,500)`` the shapes will be packed into as many 500x500 unit die as required to fit them all:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6",
   "metadata": {},
   "outputs": [],
   "source": [
    "np.random.seed(1)\n",
    "D_list = [\n",
    "    gf.components.ellipse(radii=tuple(np.random.rand(2) * n + 2)) for n in range(120)\n",
    "]\n",
    "D_packed_list = gf.pack(\n",
    "    D_list,  # Must be a list or tuple of Components.\n",
    "    spacing=4,  # Minimum distance between adjacent shapes.\n",
    "    aspect_ratio=(1, 1),  # Shape of the box.\n",
    "    max_size=(500, 500),  # Limits the size into which the shapes will be packed.\n",
    "    density=1.05,  # Values closer to 1 pack tighter but require more computation.\n",
    "    sort_by_area=True,  # Pre-sorts the shapes by area.\n",
    ")\n",
    "\n",
    "print(len(D_packed_list))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.grid(D_packed_list)\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8",
   "metadata": {},
   "source": [
    "Note that the packing problem is an NP-complete problem, so ``gf.pack()`` may be slow if there are more than a few hundred components to pack (in that case, try pre-packing a few dozen at a time then packing the resulting bins). "
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "custom_cell_magics": "kql"
  },
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
